{!{ define "git_info_job" }!}
# <template: git_info_job>
{!{/*
Outputs:
  ci_commit_tag - used as CI_COMMIT_TAG variable to publish release images.
  ci_commit_branch - used as CI_COMMIT_BRANCH to publish images for main branch and dev images.
  ci_commit_ref_name - used as image tag to run e2e and deploy-web, and for release-channel-version image.
  github_sha - used as a key for caching images_tags_*.json file.

See:
- https://docs.github.com/en/actions/learn-github-actions/environment-variables
- https://github.com/actions/toolkit/blob/main/packages/github/src/context.ts

*/}!}
{!{- $ctx := . }!}
git_info:
  name: Get git info
  runs-on: ubuntu-latest
{!{- if coll.Has $ctx "dependJobs" -}!}
{!{- if gt (len $ctx.dependJobs) 0 }!}
  needs:
{!{-   range $dep := $ctx.dependJobs }!}
{!{      printf "- %s" $dep | strings.Indent 2 }!}
{!{-   end }!}
{!{- end -}!}
{!{- end }!}
  outputs:
    ci_commit_tag: ${{ steps.git_info.outputs.ci_commit_tag }}
    ci_commit_branch: ${{ steps.git_info.outputs.ci_commit_branch }}
    ci_commit_ref_name: ${{ steps.git_info.outputs.ci_commit_ref_name }}
    ci_commit_ref_slug: ${{ steps.git_info.outputs.ci_commit_ref_slug }}
    ref_full: ${{ steps.git_info.outputs.ref_full }}
    github_sha: ${{ steps.git_info.outputs.github_sha }}
    pr_number: ${{ steps.git_info.outputs.pr_number }}
  # Skip the CI for automation PRs, e.g. changelog
  if: ${{ github.event.pull_request.user.login != 'deckhouse-BOaTswain' }}
  steps:
    - id: git_info
      name: Get tag name and SHA
      uses: {!{ index (ds "actions") "actions/github-script" }!}
      with:
        script: |
          const { GITHUB_REF_TYPE, GITHUB_REF_NAME, GITHUB_REF } = process.env

          let refSlug = ''
          let refName = ''
          let refFull = ''
          let githubBranch = ''
          let githubTag = ''
          let githubSHA = ''
          let prNumber = ''
          if (context.eventName === "workflow_dispatch" && context.payload.inputs && context.payload.inputs.pull_request_ref) {
            // Trigger: workflow_dispatch with pull_request_ref.
            // Extract pull request number from 'refs/pull/<NUM>/merge'
            prNumber = context.payload.inputs.pull_request_ref.replace('refs/pull/', '').replace('/merge', '').replace('/head', '')

            refSlug       = `pr${prNumber}`
            refName       = context.payload.inputs.ci_commit_ref_name
            refFull       = context.payload.inputs.pull_request_ref
            githubBranch  = refName
            githubSHA     = context.payload.inputs.pull_request_sha
            core.info(`workflow_dispatch event: set git info from inputs. inputs: ${JSON.stringify(context.payload.inputs)}`)
          } else if (context.eventName === "pull_request" || context.eventName === "pull_request_target" ) {
            // For PRs from forks, tag images with `prXXX` to avoid clashes between branches.
            const targetRepo = context.payload.repository.full_name;
            const prRepo = context.payload.pull_request.head.repo.full_name
            const prRef = context.payload.pull_request.head.ref

            refSlug = `pr${context.issue.number}`;
            refName = (prRepo === targetRepo) ? prRef : refSlug;
            refFull = `refs/pull/${context.issue.number}/head`
            githubBranch = refName
            githubSHA = context.payload.pull_request.head.sha
            core.info(`pull request event: set git info from pull_request.head. pr:${prRepo}:${prRef} target:${targetRepo}:${context.ref}`)
            prNumber = context.issue.number
          } else {
            // Other triggers: workflow_dispatch without pull_request_ref, schedule, push...
            // refName is 'main' or tag name, so slugification is not necessary.
            refSlug       = GITHUB_REF_NAME
            refName       = GITHUB_REF_NAME
            refFull       = GITHUB_REF
            githubTag     = GITHUB_REF_TYPE == "tag"    ? refName : ""
            githubBranch  = GITHUB_REF_TYPE == "branch" ? refName : ""
            githubSHA     = context.sha
            core.info(`${context.eventName} event: set git info from context: ${JSON.stringify({GITHUB_REF_NAME, GITHUB_REF_TYPE, sha: context.sha })}`)
          }

          core.setCommandEcho(true)
          core.setOutput('ci_commit_ref_slug', refSlug)
          core.setOutput('ci_commit_ref_name', refName)
          core.setOutput(`ci_commit_tag`, githubTag)
          core.setOutput(`ci_commit_branch`, githubBranch)
          core.setOutput(`ref_full`, refFull)
          core.setOutput('github_sha', githubSHA)
          core.setOutput('pr_number', prNumber)
          core.setCommandEcho(false)

# </template: git_info_job>
{!{- end -}!}


# Check pull request state on push or pull_request_target events:
# - find PR info on push event
# - detect edition from PR labels
# - calculate ref to use in further checkout jobs
# - detect if PR is 'external': checkout the head commit and fail if changes are not safe
{!{ define "pull_request_info_job" }!}
# <template: pull_request_info>
pull_request_info:
  name: Get pull request reference
  runs-on: ubuntu-latest
  outputs:
    ref: ${{ steps.pr_props.outputs.ref }}
    ref_slug: ${{ steps.pr_props.outputs.ref_slug }}
    edition: ${{ steps.pr_props.outputs.edition }}
    pr_title: ${{ steps.pr_props.outputs.pr_title }}
    pr_description: ${{ steps.pr_props.outputs.pr_description }}
    diff_url: ${{ steps.pr_props.outputs.diff_url }}
    labels: ${{ steps.pr_props.outputs.labels }}
    changes_docs: ${{ steps.changes.outputs.docs }}
    changes_not_markdown: ${{ steps.changes.outputs.not_markdown }}

  # Skip pull_request and pull_request_target triggers for PRs authored by deckhouse-BOaTswain, e.g. changelog PRs.
  if: ${{ ! (startsWith(github.event_name, 'pull_request') && github.event.pull_request.user.login == 'deckhouse-BOaTswain') }}
  steps:
    - name: Get PR info for push trigger
      id: push_info
      if: ${{ github.event_name == 'push' }}
      uses: {!{ index (ds "actions") "actions/github-script" }!}
      with:
        script: |
          // Support for 'push' trigger: find PR by commit SHA and pass response to pr_props step.
          const { GITHUB_REF_NAME } = process.env
          core.startGroup(`Fetching PR info for commit ${context.sha} in ${context.repo.name}:${GITHUB_REF_NAME} ...`)
          try {
            const response = await github.rest.repos.listPullRequestsAssociatedWithCommit({
                owner: context.repo.owner,
                repo: context.repo.repo,
                commit_sha: context.sha
            });
            if (response.status !== 200 || !response.data || response.data.length === 0) {
              return core.setFailed(`Bad response on listing PRs for commit ${context.sha}: ${JSON.stringify(response)}`);
            }
            // Get first associated pr.
            let pr = response.data[0];
            core.info(`Current labels: ${JSON.stringify(pr.labels)}`);
            // Reduce size to fit output limits.
            pr = {
              url:      pr.url,
              diff_url: pr.diff_url,
              number:   pr.number,
              labels:   pr.labels,
              head:     pr.head,
              title:    pr.title,
              body:     pr.body,
            }
            core.notice(`Found PR#{pr.number} for commit ${context.sha}`);
            core.setOutput('pr_info', JSON.stringify(pr));
          } catch (error) {
            return core.setFailed(`Error listing pull requests for commit ${context.sha}: ${error}`)
          } finally {
            core.endGroup()
          }

    - name: Get PR info for pull_request trigger
      id: pr_info
      if: ${{ startsWith(github.event_name, 'pull_request') }}
      uses: {!{ index (ds "actions") "actions/github-script" }!}
      with:
        script: |
          // Support for 'pull_request' and 'pull_request_target' triggers:
          // find PR by its number to get current labels.
          // Why? Workflow rerun of 'opened' pull request contains outdated labels.
          const prNumber = context.payload.pull_request.number;
          const owner = context.repo.owner;
          const repo = context.repo.repo;
          core.startGroup(`Fetching info for PR#${prNumber} ...`);
          try {
            const response = await github.rest.pulls.get({owner, repo, pull_number: prNumber})
            if (response.status != 200 || !response.data) {
              return core.setFailed(`Bad response on getting PR#${prNumber} : ${JSON.stringify(response)}`);
            }
            // Only labels are needed.
            let pr = response.data;
            core.info(`Labels from context: ${JSON.stringify(context.payload.pull_request.labels)}`);
            core.info(`Current labels: ${JSON.stringify(pr.labels)}`);
            // Reduce size to fit output limits.
            pr = {
              url:      pr.url,
              diff_url: pr.diff_url,
              number:   pr.number,
              labels:   pr.labels,
              head:     pr.head,
              title:    pr.title,
              body:     pr.body,
            }
            core.setOutput('pr_info', JSON.stringify(pr));
          } catch (error) {
            return core.setFailed(`Fetch PR#${prNumber} error: ${error}`)
          } finally {
            core.endGroup()
          }

    - name: Check PR properties
      id: pr_props
      uses: {!{ index (ds "actions") "actions/github-script" }!}
      env:
        PR_INFO: ${{ steps.push_info.outputs.pr_info || steps.pr_info.outputs.pr_info }}
      with:
        script: |
          if (process.env.PR_INFO == '') {
              return core.setFailed(`No pull request info: event_name=${context.eventName} action=${context.action} ref=${context.ref}`);
          }
          // Parse Pr info from environment variable.
          const pr = JSON.parse(process.env.PR_INFO);

          core.startGroup(`Detect PR properties`)
          const pr_repo = pr.head.repo.full_name;
          const target_repo = context.payload.repository.full_name;
          const isInternal = pr_repo === target_repo;
          const isDependabot = (context.actor === 'dependabot[bot]');
          const isChangelog = pr.head.ref.startsWith('changelog/v');
          const okToTest = pr.labels.some((l) => l.name === 'status/ok-to-test');
          core.info(`PR head repo          ${pr_repo}`)
          core.info(`PR commit SHA         ${pr.head.sha}`)
          core.info(`PR head label         ${pr.head.label}`)
          core.info(`Target repo           ${target_repo}`)
          core.info(`PR internal?          ${isInternal}`)
          core.info(`PR from dependabot?   ${isDependabot}`)
          core.info(`PR changelog?         ${isChangelog}`)
          core.info(`PR has 'ok-to-test'?  ${okToTest}`)
          core.endGroup()

          // Detect if PR can be ignored or should be checked for dangerous changes.
          let shouldCheckFiles = false;
          if (isInternal && !isDependabot) {
            // Ignore changelog pull requests.
            if (isChangelog) {
              return core.setFailed(`PR#${pr.number} for changelog is ignored.`);
            }
          } else {
            // External and dependabot pull requests should be labeled with 'status/ok-to-test'.
            if (!okToTest) {
              core.notice(`PR#${pr.number} requires label 'status/ok-to-test' to run tests and validations`)
              return core.setFailed(`PR#${pr.number} without label 'status/ok-to-test' is ignored.`);
            }
            shouldCheckFiles = true;
          }
          if (shouldCheckFiles) {
            core.notice(`PR#{pr.number} may be dangerous, will check file changes.`)
          }

          // Set edition from current labels.
          const defaultEdition = process.env.WERF_ENV ? process.env.WERF_ENV : 'FE';
          const hasEE = pr.labels.some((l) => l.name === 'edition/ee');
          const hasCE = pr.labels.some((l) => l.name === 'edition/ce');
          const hasBE = pr.labels.some((l) => l.name === 'edition/be');
          const hasSE = pr.labels.some((l) => l.name === 'edition/se');
          let edition = defaultEdition;
          if (hasCE) {
            edition = 'CE';
          } else if (hasEE) {
            edition = 'EE';
          } else if (hasBE) {
            edition = 'BE';
          } else if (hasSE) {
            edition = 'SE';
          }
          core.info(`Edition labels: 'edition/ce':${hasCE}, 'edition/ee':${hasEE}, 'edition/be':${hasBE}, 'edition/se':${hasSE}`);
          core.notice(`Enable '${edition}' edition for '${context.eventName}' trigger.`);

          // Construct head commit ref using pr number.
          const ref = `refs/pull/${ pr.number }/head`;
          core.notice(`Use ref: '${ref}'`)

          // Pass pr.diff_url to download diff via regular request.
          // Pass pr.url to get diff via API request.
          let diff_url = pr.diff_url;
          if (!!context.payload.repository.private) {
            core.notice(`Detect private repo. Pass PR url to download diff via Github API.`);
            diff_url = pr.url;
          }

          // Set outputs.
          core.setCommandEcho(true)
          core.setOutput('should_check', shouldCheckFiles.toString());
          core.setOutput('ref', ref);
          core.setOutput('ref_slug', `pr${pr.number}`);
          core.setOutput('edition', edition);
          core.setOutput('pr_title', pr.title);
          core.setOutput('pr_description', pr.body);
          core.setOutput('diff_url', diff_url);
          core.setOutput('labels', JSON.stringify(pr.labels));
          core.setCommandEcho(false);

    # Checkhout the head commit of the PR branch.
    - name: Checkout PR head commit
      if: steps.pr_props.outputs.should_check == 'true'
      uses: {!{ index (ds "actions") "actions/checkout" }!}
      with:
        ref: ${{ steps.pr_props.outputs.ref }}

    # Get info about other changes.
    - name: Get info about PR changes
      uses: {!{ index (ds "actions") "dorny/paths-filter" }!}
      id: changes
      with:
        token: ${{ secrets.BOATSWAIN_GITHUB_TOKEN }}
        # dangerous - detect if changes not allowed to test for external PRs
        # docs - detect changes in files that belong to the documentation scope
        # not_markdown - detect changes not in markdown files
        filters: |
          dangerous:
            - './.github/**'
            - './tools/**'
            - './testing/**'
            - './docs/**/js/**'
            - './docs/**/css/**'
            - './docs/**/images/**'
            - './docs/**/assets/**'
          docs:
            - './**/*.md'
            - './docs/**'
            - './**/crds/*'
            - './**/openapi/*config-values.yaml'
            - './candi/**/openapi/*'
            - './ee/candi/**/openapi/*'
          not_markdown:
            - '!./**/*.md'

    # Stop workflow if external PR contains dangerous changes.
    - name: Fail workflow on dangerous changes
      if: ${{ steps.pr_props.outputs.should_check == 'true' && steps.changes.outputs.dangerous == 'true' }}
      uses: {!{ index (ds "actions") "actions/github-script" }!}
      with:
        script: |
          core.setFailed('External PR contains dangerous changes.')

# </template: pull_request_info>
{!{- end -}!}
